﻿using System;
using System.Diagnostics;
using System.Text;
using System.IO;
using SlimDX;
using System.Windows.Forms;
using System.Reflection;

namespace BasicFramework.Helper
{

    public enum LoggingLevel
    {
        Low,
        Medium,
        High
    }

    /// <summary>
    /// Die Klasse schreibt die Fehlermeldungen in eine Datei.
    /// </summary>
    public class Logger
    {

        #region Fields

        private static LoggingLevel logLevel;
        private static string logFile;
        private static bool writeDateTimeEverytime;
        private string runningAssembly;


        #endregion


        #region Properties

        /// <summary>
        /// Eigenschaft: Ruft den aktuellen Namen der Protokolldatei ab. (schreibgeschützt)
        /// </summary>
        public static string LogFile
        {
            get { return logFile; }
        }

        /// <summary>
        /// Eigenschaft: Ruft den aktuellen Namen der Protokolldatei ab. (schreibgeschützt)
        /// </summary>
        public static bool WriteDateTimeEverytime
        {
            get { return writeDateTimeEverytime; }
            set { writeDateTimeEverytime = value; }
        }

        /// <summary>
        /// Eigenschaft: Liest und setzt das Protokoll-Level
        /// </summary>
        public static LoggingLevel LogLevel
        {
            get { return logLevel; }
            set { logLevel = value; }
        }


        #endregion

        // TODO: TextFormatierung gerade ziehen !

        #region Constructors

        ///<summary>
        /// Standardkonstruktor: Er legt den Dateinamen nach den Standardkonvention der Klasse fest
        /// </summary>
        public Logger()
        {
            // Standardwert für Protokoll-Level festlegen
            logLevel = LoggingLevel.High;

            // Pfadname der Log-Datei wird mit einem StrinBuilder-Objekt zusammengebaut
            StringBuilder sb = new StringBuilder();

            // Pfad der ausgeführten Anwendung holen (ohne Dateinamen)
            sb.Append(Application.StartupPath);

            // Slash anhängen
            sb.Append("\\");

            // Datum im Kurzformat anhängen
            sb.Append(DateTime.Now.ToShortDateString());

            // Unterstrich anhängen
            sb.Append("_");

            // Namen der exe-Datei ermitteln und anhängen
            sb.Append(Path.GetFileName(Application.ExecutablePath));

            // Erweiterung .log anhöngen
            sb.Append(".log");

            // Name in der Klassenvariablen ablegen
            logFile = sb.ToString();

            StreamWriter sw = OpenLogFile();
            try
            {
                if (sw != null)
                {
                    WriteInitialLog(sw);
                }
            }
            finally
            {
                if (sw != null)
                {
                    sw.Close();
                }
            }
        }

        public Logger(Module module)
        {
            runningAssembly = module.Assembly.ManifestModule.Name + "[" + module.Assembly.FullName + "]";

            // Standardwert für Protokoll-Level festlegen
            logLevel = LoggingLevel.High;

            // Pfadname der Log-Datei wird mit einem StrinBuilder-Objekt zusammengebaut
            StringBuilder sb = new StringBuilder();

            // Pfad der ausgeführten Anwendung holen (ohne Dateinamen)
            sb.Append(Application.StartupPath);

            // Slash anhängen
            sb.Append("\\");

            // Datum im Kurzformat anhängen
            sb.Append(DateTime.Now.ToShortDateString());

            // Unterstrich anhängen
            sb.Append("_");

            // Namen der exe-Datei ermitteln und anhängen
            sb.Append(Path.GetFileName(Application.ExecutablePath));

            // Erweiterung .log anhöngen
            sb.Append(".log");

            // Name in der Klassenvariablen ablegen
            logFile = sb.ToString();

            StreamWriter sw = OpenLogFile();
            try
            {
                if (sw != null)
                {
                    WriteInitialLog(sw);
                }
            }
            finally
            {
                if (sw != null)
                {
                    sw.Close();
                }
            }
        }

        /// <summary>
        /// Konstruktor, mit dem Pfad/Name der Protokolldatei festgelegt werden kann.
        /// </summary>
        public Logger(string logFilePath)
        {
            // Standardwert für Protokoll-Level festlegen
            logLevel = LoggingLevel.High;

            // Dateinamen so eintragen, wie er an den Konstruktor übergeben wird
            logFile = logFilePath;

            StreamWriter sw = OpenLogFile();
            try
            {
                if (sw != null)
                {

                    WriteInitialLog(sw);
                }
            }
            finally
            {
                if (sw != null)
                {
                    sw.Close();
                }
            }
        }

        /// <summary>
        /// Konstruktor, mit dem der Pfad und das LoggingLevel festgelegt werden kann.
        /// </summary>
        public Logger(string logFilePath, LoggingLevel level)
        {
            logFile = logFilePath;
            logLevel = level;

            StreamWriter sw = OpenLogFile();
            try
            {
                if (sw != null)
                {
                    WriteInitialLog(sw);
                }
            }
            finally
            {
                if (sw != null)
                {
                    sw.Close();
                }
            }
        }

        /// <summary>
        /// Konstruktor, mit dem der Pfad und das LoggingLevel festgelegt werden kann.
        /// </summary>
        public Logger(string logFilePath, LoggingLevel level, bool writeDateEverytime)
        {
            logFile = logFilePath;
            logLevel = level;
            writeDateTimeEverytime = writeDateEverytime;

            StreamWriter sw = OpenLogFile();
            try
            {
                if (sw != null)
                {
                    WriteInitialLog(sw);
                }
            }
            finally
            {
                if (sw != null)
                {
                    sw.Close();
                }
            }

        }

        /// <summary>
        /// Konstruktor, mit dem der Pfad und das LoggingLevel festgelegt werden kann.
        /// </summary>
        public Logger(LoggingLevel level, bool writeDateEverytime)
        {
            // Pfadname der Log-Datei wird mit einem StrinBuilder-Objekt zusammengebaut
            StringBuilder sb = new StringBuilder();

            // Pfad der ausgeführten Anwendung holen (ohne Dateinamen)
            sb.Append(Application.StartupPath);

            // Slash anhängen
            sb.Append("\\");

            // Datum im Kurzformat anhängen
            sb.Append(DateTime.Now.ToShortDateString());

            // Unterstrich anhängen
            sb.Append("_");

            // Namen der exe-Datei ermitteln und anhängen
            sb.Append(Path.GetFileName(Application.ExecutablePath));

            // Erweiterung .log anhöngen
            sb.Append(".log");

            // Name in der Klassenvariablen ablegen
            logFile = sb.ToString();

            logLevel = level;
            writeDateTimeEverytime = writeDateEverytime;

            StreamWriter sw = OpenLogFile();
            try
            {
                if (sw != null)
                {
                    WriteInitialLog(sw);
                }
            }
            finally
            {
                if (sw != null)
                {
                    sw.Close();
                }
            }

        }



        #endregion


        #region Public Methods



        public static void Error(string errorMessage)
        {
            Error(errorMessage, logLevel);
        }

        private static void Error(string errorMessage, LoggingLevel level)
        {
            // Datei öffnen lassen 
            StreamWriter sw = OpenLogFile();

            // Wenn das öffnen erfolgreich war:
            if (sw != null)
            {
                try
                {
                    // Datum und Uhrzeit wegschreiben
                    if (writeDateTimeEverytime)
                    {
                        WriteDateTime(sw);
                    }

                    StackFrame frame = new StackFrame(2);
                    Type callingType = frame.GetMethod().DeclaringType;
                    string callingMethod = frame.GetMethod().Name;
                    Module module = frame.GetMethod().Module;

                    // Informationen über die Ausnahme wegschreiben
                    WriteError(sw, level, errorMessage, callingType, callingMethod, module);

                }
                finally
                {
                    // Datei wieder schließen und freigeben
                    sw.Close();

                }

            }
        }


        public static void Exception(Exception ex)
        {
            Exception(ex, logLevel);
        }


        private static void Exception(Exception ex, LoggingLevel level)
        {
            // Datei öffnen lassen 
            StreamWriter sw = OpenLogFile();

            // Wenn das öffnen erfolgreich war:
            if (sw != null)
            {
                try
                {
                    // Datum und Uhrzeit wegschreiben
                    if (writeDateTimeEverytime)
                    {
                        WriteDateTime(sw);
                    }

                    StackFrame frame = new StackFrame(2);
                    Type callingType = frame.GetMethod().DeclaringType;
                    string callingMethod = frame.GetMethod().Name;
                    Module module = frame.GetMethod().Module;

                    // Informationen über die Ausnahme wegschreiben
                    WriteException(sw, level, ex, callingType, callingMethod, module);

                }
                finally
                {
                    // Datei wieder schließen und freigeben
                    sw.Close();

                }

            }


        }

        public static void Info(string msg)
        {
            Info(msg, logLevel);
        }

        private static void Info(string msg, LoggingLevel level)
        {

            // Datei öffnen lassen 
            StreamWriter sw = OpenLogFile();

            // Wenn das öffnen erfolgreich war:
            if (sw != null)
            {
                try
                {
                    if (writeDateTimeEverytime)
                    {
                        // Datum und Uhrzeit wegschreiben
                        WriteDateTime(sw);
                    }

                    StackFrame frame = new StackFrame(2);
                    Type callingType = frame.GetMethod().DeclaringType;
                    string callingMethod = frame.GetMethod().Name;
                    Module module = frame.GetMethod().Module;
                    // Informationen über die Ausnahme wegschreiben
                    WriteInfo(sw, level, msg, callingType, callingMethod, module);

                }
                finally
                {
                    // Datei wieder schließen und freigeben
                    sw.Close();

                }

            }

        }

        #endregion


        #region Write Methods

        private void WriteInitialLog(TextWriter sw)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(DateTime.Now.ToShortDateString());
            sb.Append(" - ");
            sb.Append(DateTime.Now.ToLongTimeString());

            sw.WriteLine("########################");
            sw.WriteLine("#                       #");
            sw.WriteLine("#                       #");
            sw.WriteLine("#   Logging Started     #");
            sw.WriteLine("#                       #");
            sw.WriteLine("#         at            #");
            sw.WriteLine("#                       #");
            sw.WriteLine("# " + sb + " #");
            sw.WriteLine("#########################");

            if (!String.IsNullOrEmpty(this.runningAssembly))
            {
                sw.WriteLine();
                sw.Write("Logging started for : " + this.runningAssembly);
                sw.WriteLine();
            }
        }


        private static void WriteDateTime(TextWriter sw)
        {
            sw.WriteLine("_______________________________");
            StringBuilder sb = new StringBuilder();
            sb.Append(DateTime.Now.ToShortDateString());
            sb.Append(" - ");
            sb.Append(DateTime.Now.ToLongTimeString());
            sw.WriteLine(sb.ToString());
        }

        private static void WriteSpace(TextWriter sw)
        {
            sw.Write("              ");
        }


        private static void WriteSmallTimeStamp(TextWriter sw)
        {
            sw.Write(DateTime.Now.ToLongTimeString() + " : ");
        }



        private static void WriteInfo(TextWriter sw, LoggingLevel level, string errorMessage, Type callingType, string callingMethod, Module mod)
        {
            WriteSmallTimeStamp(sw);
            sw.Write(mod.Assembly.ManifestModule.Name + " | Class: " + callingType.Name + " | Method: " + callingMethod);
            WriteSpace(sw);
            sw.Write("INFO:      ");
            WriteSpace(sw);
            sw.Write(errorMessage);
            //sw.WriteLine();
        }



        private static void WriteException(TextWriter sw, LoggingLevel level, Exception ex, Type callingType, string callingMethod, Module mod)
        {
            WriteSmallTimeStamp(sw);
            sw.Write(mod.Assembly.ManifestModule.Name + " | Class: " + callingType.Name + " | Method: " + callingMethod);
            WriteSpace(sw);
            sw.Write("EXCEPTION: ");
            WriteSpace(sw);
            sw.Write(ex.Message);
            sw.Write(" | StackTrace: " + ex.StackTrace);
            //sw.WriteLine();

        }

        private static void WriteError(TextWriter sw, LoggingLevel level, string errorMessage, Type callingType, string callingMethod, Module mod)
        {
            WriteSmallTimeStamp(sw);
            sw.Write(mod.Assembly.ManifestModule.Name + " | Class: " + callingType.Name + " | Method: " + callingMethod);
            WriteSpace(sw);
            sw.Write("ERROR:     ");
            WriteSpace(sw);
            sw.Write(errorMessage);
            //sw.WriteLine();
        }



        #endregion


        #region Helper Methods

        /// <summary>
        /// Öffnet die Logdatei und fügt eine Leerzeile hinzu.
        /// Ist die Logdatei nicht vorhanden wird diese erstellt.
        /// </summary>
        /// <returns>Öffnet die Logdatei und gibt diese zurück.
        /// Sollte ein Fehler auftauchen gibt die Datei null zurück.</returns>
        private static StreamWriter OpenLogFile()
        {
            StreamWriter sw;

            // wenn Datei schon vorhanden ist
            if (File.Exists(logFile))
            {
                // Datei im Append-Modus öffnen
                sw = File.AppendText(logFile);

                // hinter dem letzten Eintrag der Datei eine Leerzeile einfügen
                sw.WriteLine();


            }
            // Neue Datei erstellen, wenn Sie noch nicht vorhanden ist
            else
            {
                sw = File.CreateText(logFile);
            }

            // Streamwrite zurückgeben, kann null sein
            return sw;


        }

        #endregion



    }
}
